package com.hrm.utils.core;

import com.alibaba.fastjson.JSON;
import com.baidu.aip.face.AipFace;
import com.baidu.aip.face.MatchRequest;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;

import java.util.*;

/**
 * 调用百度人脸识别api，用于人脸登录、注册。
 *
 * @author mumu
 * @date 2020/1/23
 */
@Slf4j
public class FaceClient {

    private FaceClient() {
    }

    /**
     * 常用百度api返回的常用json属性
     */
    private static final String FACE_TOKEN = "face_token";
    private static final String USER_ID = "user_id";
    private static final String USER_LIST = "user_list";
    private static final String SCORE = "score";
    private static final String ERR_MSG = "error_msg";
    private static final String ERR_CODE = "error_code";
    private static final String RESULT = "result";

    /**
     * 百度api错误信息头
     */
    private final String ERROR_MSG_HEADER = "百度api错误:";

    private AipFace client;
    /**
     * 默认分数，两个人脸数据匹配成绩达到该分数即为同一张脸。
     */
    private final Float DEFAULT_SCORE = 80f;

    private static FaceClient instance = new FaceClient();

    /**
     * 将错误代码与错误信息对应
     */
    private static Map<String, String> MSG_MAP = new HashMap<>();

    static {
        MSG_MAP.put("222202", "图片中没有人脸");
        MSG_MAP.put("222203", "无法解析人脸");
        MSG_MAP.put("223106", "人脸图片不存在");
        MSG_MAP.put("222210", "超过用户人脸存储量");
    }

    public FaceClient config(String appId, String apiKey, String secretKey) {
        this.client = new AipFace(appId, apiKey, secretKey);
        return this;
    }

    /**
     * 获取单例的人脸识别客户端
     *
     * @param appId
     * @param apiKey
     * @param secretKey
     * @return
     */
    public static FaceClient newInstance(String appId, String apiKey, String secretKey) {
        return instance.config(appId, apiKey, secretKey);
    }

    /**
     * 人脸检测，图片是否含有头像以及头像属性
     *
     * @param image
     * @param imageType
     * @param options
     * @return
     */
    public JSONObject detect(String image, String imageType, HashMap<String, String> options) {
        JSONObject res = client.detect(image, imageType, options);
        return check(res);
    }

    /**
     * 在人脸库中进行搜索，取结果列表中的第一个结果。
     *
     * @param base64 图片数据经过Base64编码后的字符串
     * @return JSONObject
     */
    public JSONObject search(String base64, String imageType, String group, HashMap<String, String> options) {
        JSONObject jsonObject = client.search(base64, imageType, group, options);
        log.info("人脸库查询结果：" + JSON.toJSONString(jsonObject));
        return Optional.ofNullable(jsonObject)
                .map(object -> check(object))
                .map(integer -> jsonObject.has(RESULT))
                .map(b -> b ? jsonObject.getJSONObject(RESULT) : null)
                .map(object -> object.getJSONArray(USER_LIST))
                .map(array -> array.getJSONObject(0))
                .orElseThrow(() -> new FaceClientException("没有匹配到结果，你可能没有人脸注册。"));
    }

    /**
     * 获取百度api返回的json中的匹配分数，并与设置的分数线比较。
     * 为认证验证
     *
     * @param object
     * @param score
     * @return
     */
    public boolean verify(JSONObject object, float score) {
        log.info("1:1比对分数score=" + getStringWithResult(object, SCORE));
        return Float.parseFloat(getStringWithResult(object, SCORE)) - score >= 0;
    }

    public boolean verify(JSONObject object) {
        return verify(object, DEFAULT_SCORE);
    }

    /**
     * 为人脸搜索验证
     *
     * @param object
     * @param score
     * @return
     */
    public boolean verifyForSearch(JSONObject object, float score) {
        log.info("人脸库查询比对分数score=" + getString(object, SCORE));
        return Float.parseFloat(getString(object, SCORE)) - score >= 0;
    }

    public boolean verifyForSearch(JSONObject object) {
        return verifyForSearch(object, DEFAULT_SCORE);
    }


    /**
     * 获取百度api返回的json中的某一字段的值（String类型）。该函数能进行null检查。
     * result为key值的
     *
     * @param object
     * @param field
     * @return String
     */
    public String getStringWithResult(JSONObject object, String field) {
        JSONObject resultObject = object.getJSONObject("result");
        return String.valueOf(
                Optional.ofNullable(resultObject)
                        .map(o -> o.has(field))
                        .map(b -> b ? resultObject.get(field) : null)
                        .orElseThrow(() -> new FaceClientException("无法从json中获取" + field)));
    }

    /**
     * 获取百度api返回的json中的某一字段的值（String类型）。该函数能进行null检查。
     *
     * @param object
     * @param field
     * @return String
     */
    public String getString(JSONObject object, String field) {
        return String.valueOf(
                Optional.ofNullable(object)
                        .map(o -> o.has(field))
                        .map(b -> b ? object.get(field) : null)
                        .orElseThrow(() -> new FaceClientException("无法从json中获取" + field)));
    }

    public boolean match(String imgStr1, String imgStr2, String imageType) {
        return match(imgStr1, imgStr2, imageType, DEFAULT_SCORE);
    }

    /**
     * 使用百度api进行人脸图片比较
     *
     * @param imgStr1
     * @param imgStr2
     * @param imageType
     * @return
     */
    public boolean match(String imgStr1, String imgStr2, String imageType, float score) {
        MatchRequest req1 = new MatchRequest(imgStr1, imageType);
        MatchRequest req2 = new MatchRequest(imgStr2, imageType);
        List<MatchRequest> requests = new ArrayList<MatchRequest>();
        requests.add(req1);
        requests.add(req2);
        JSONObject res = client.match(requests);
        log.info(JSON.toJSONString(res));
        return verify(res, score);
    }

    /**
     * 向百度api添加人脸数据，即完成人脸注册。
     *
     * @param id     用于百度人脸库的中的user_id
     * @param base64 图片数据经过Base64编码后的字符串
     * @return 百度api返回的face_token
     */
    public String addFace(Integer id, String base64, String imageType, String group, HashMap<String, String> options) {
        JSONObject res = client.addUser(base64, imageType, group, String.valueOf(id), options);
        log.info("注册一个用户人脸结果：" + JSON.toJSONString(res));
        return getStringWithResult(check(res), FACE_TOKEN);
    }

    /**
     * 人脸更新
     * 针对一个user_id执行更新操作，新上传的人脸图像将覆盖此user_id原有所有图像。
     *
     * @param id
     * @param base64
     * @param imageType
     * @param group
     * @param options
     * @return JSONObject
     */
    public String updateFace(Integer id, String base64, String imageType, String group, HashMap<String, String> options) {
        JSONObject res = client.updateUser(base64, imageType, group, String.valueOf(id), options);
        log.info("修改一个用户结果：" + JSON.toJSONString(res));
        return getStringWithResult(check(res), FACE_TOKEN);
    }

    /**
     * 删除一张人脸信息
     * 删除用户的某一张人脸，如果该用户只有一张人脸图片，则同时删除用户。
     *
     * @param id
     * @param group
     * @param faceToken
     * @param options
     * @return boolean
     */
    public boolean deleteFace(Integer id, String group, String faceToken, HashMap<String, String> options) {
        JSONObject res = client.faceDelete(String.valueOf(id), group, faceToken, options);
        log.info("修改一个用户结果：" + JSON.toJSONString(res));
        int code = res.getInt(ERR_CODE);
        if (code != 0) {
            log.info("删除失败，原因：" + res.getString(ERR_MSG));
            return false;
        }
        return true;
    }


    /**
     * 检查百度api是否出错，出错则抛出异常。
     *
     * @param jsonObject 百度api返回的json
     */
    private JSONObject check(JSONObject jsonObject) {
        return Optional.ofNullable(jsonObject)
                .map(object -> object.has(ERR_CODE))
                .map(b -> b ? jsonObject.getInt(ERR_CODE) : -1)
                .map(integer -> integer != 0 ? null : jsonObject)
                .orElseThrow(() ->
                        new FaceClientException(ERROR_MSG_HEADER + MSG_MAP.get(getString(jsonObject, ERR_CODE))));
    }
}
